﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using ArcEngine.Forms;
using ArcEngine.Resources;
using ArcEngine.Time;
using Tao.OpenGl;
using Tao.Platform.Windows;

//
//
//
// - Screen.PrimaryScreen.WorkingArea property, which holds information about your screen width/height etc.
// ChangeDisplaySettings : Permet de changer les réglages d’un périphérique d’affichage. Prend en entrée le nom du périphérique (retourné par EnumDisplayDevices) ainsi que les réglages désirés.
// EnumDisplayDevices : Retourne les périphériques d’affichage de la session courante.
// EnumDisplaySettings : Retourne les réglages actuel du périphérique donnés en entrée (EnumDisplayDevices)
// http://www.pinvoke.net/default.aspx/user32/ChangeDisplaySettingsEx.html
//
// http://www.gamedev.net/community/forums/topic.asp?topic_id=418397
// http://www.pinvoke.net/default.aspx/coredll/ChangeDisplaySettingsEx.html
// http://www.codeproject.com/KB/cs/csdynamicscrres.aspx
//
//
//
// - I want my main loop: driving animation using Application.Idle : 
//   http://www.opentk.com/doc/chapter/2/glcontrol
//
// - http://hge.relishgames.com/
//
// - Rounded rectangle : http://www.experts-exchange.com/Programming/Game/Game_Graphics/OpenGL/Q_21660812.html
//
//
// - Game State : http://gamedevgeek.com/tutorials/managing-game-states-in-c/
//
// http://gamasutra.com/features/20060804/boutros_01.shtml
//
//
namespace ArcEngine.Graphics
{
	/// <summary>
	/// Main Video class
	/// </summary>
	public static class Video
	{

		/// <summary>
		/// Initializes the video subset
		/// </summary>
		static public bool Init()
		{
			if (isInit)
				return true;

			// Init the OpenGL context
			HiddenGlControl = new SimpleOpenGlControl();
			HiddenGlControl.InitializeContexts();
			HiddenGlControl.MakeCurrent();
			HiddenGlControlContext = Wgl.wglGetCurrentContext();

			InitOpenGLExtensions();


			// Init devices
			Input.Keyboard.Init(formHandle);

			// DevIL
			Tao.DevIl.Il.ilInit();

			isInit = true;
			return true;
		}


		/// <summary>
		/// Opens the display render
		/// </summary>
		/// <param name="size"></param>
		/// <param name="fullscreen"></param>
		/// <returns></returns>
		public static bool OpenRender(Size size, bool fullscreen)
		{
			formHandle = new RenderForm(); 
			formHandle.GlControl.Size = size;
			formHandle.Show();
			formHandle.BringToFront();

			// Rendering states
			Texturing = true;
			Blending = true;
			ClearColor = Color.CornflowerBlue;
			Culling = false;
			DepthTest = false;

			// Raw opengl states
			Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);


			//Terminal.Log(new LogEventArgs(LogLevel.Info, "GL_VENDOR :" + VideoVendor, null));
			//Terminal.Log(new LogEventArgs(LogLevel.Info, "GL_RENDERER :" + VideoRenderer, null));
			//Terminal.Log(new LogEventArgs(LogLevel.Info, "GL_VERSION :" + VideoVersion, null));
			//Terminal.Log(new LogEventArgs(LogLevel.Info, "GL_SHADING_LANGUAGE_VERSION :" + ShadingLangageVersion, null));
			//Terminal.Log(new LogEventArgs(LogLevel.Info, "GL_EXTENSIONS :" + VideoExtensions, null));

			// Share the video context
			formHandle.GlControl.MakeCurrent();
			Video.ShareVideoContext();

			
			// http://www.opentk.com/doc/chapter/2/glcontrol
			//Stopwatch sw = new Stopwatch();



			return true;
		}



		/// <summary>
		/// Closes the rendering form
		/// </summary>
		public static void CloseRender()
		{
			formHandle.Close();
			formHandle.Dispose();
			formHandle = null;
		}


		/// <summary>
		/// Starts the framerate ticker
		/// </summary>
		public static void Run()
		{
			GameTime.Update();
			Application.Run(formHandle);
		}


		/// <summary>
		/// Update the engine
		/// </summary>
		static internal void Update()
		{
			GameTime.Update();



			if (OnFrameTick != null)
				OnFrameTick(null, null);
		}


		/// <summary>
		/// Quits application by raising and quit event. 
		/// </summary>
		static public void Quit()
		{
			if (OnClosing != null)
			{
				QuitEventArgs ev = new QuitEventArgs();
				OnClosing(null, ev);
			}
			Application.Exit();
		}


		#region Fading


		/// <summary>
		/// Fades the screen to black
		/// </summary>
		/// <param name="maxVelocity">MaxVelocity in milliseconds</param>
		static public void FadeToBlack(int speed)
		{
			System.Threading.Thread.Sleep(speed);
		}


		/// <summary>
		/// Fades the screen to a specified color
		/// </summary>
		/// <param name="col">Fade to color</param>
		/// <param name="maxVelocity">MaxVelocity in milliseconds</param>
		static public void FadeTo(Color col, int speed)
		{
			System.Threading.Thread.Sleep(speed);
		}


		#endregion


		#region OpenGL

		/// <summary>
		/// Clears buffers
		/// </summary>
		static public void ClearBuffers()
		{
			Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
		}



		/// <summary>
		/// Swap buffers
		/// </summary>
		static public void SwapBuffers()
		{
			formHandle.GlControl.SwapBuffers();
		}


		/// <summary>
		/// Init all opengl extensions needed
		/// </summary>
		private static void InitOpenGLExtensions()
		{
			Wgl.ReloadFunctions();
			
			nonPowerOfTwoTextureSupported = Gl.IsExtensionSupported("GL_ARB_texture_non_power_of_two");

			WaitVbl = true;
		}


		/// <summary>
		/// Gets Opengl errors
		/// </summary>
		static public void GetGlError(string command)
		{
			int error = Gl.glGetError();
			if (error == Gl.GL_NO_ERROR)
				return;


			StackFrame stack = new StackFrame(1, true);

			string msg = command + " => ";
			switch (error)
			{
				case Gl.GL_INVALID_ENUM:
				msg += "GL_INVALID_ENUM";
				break;

				case Gl.GL_INVALID_VALUE:
				msg += "GL_INVALID_VALUE";
				break;

				case Gl.GL_INVALID_OPERATION:
				msg += "GL_INVALID_OPERATION";
				break;

				default:
				msg += error.ToString();
				break;
			}


			Log.Send(new LogEventArgs(LogLevel.Error, "\"" + stack.GetFileName() + ":" + stack.GetFileLineNumber() + "\" => GL error : " + msg + " ("+error + ")", null));

		}


		/// <summary>
		/// Advanced mode
		/// Shares the rendering context (OpenGl) with another OpenGl instance
		/// </summary>
		static public void ShareVideoContext()
		{
			Wgl.wglShareLists(HiddenGlControlContext, Wgl.wglGetCurrentContext());
		}


		#endregion


		#region Blitting

		/// <summary>
		/// Draws a colored rectangle
		/// </summary>
		/// <param name="rect">Rectangle to draw</param>
		/// <param name="fill">Fill in the rectangle or not</param>
		static public void Rectangle(Rectangle rect, bool fill)
		{
			Texturing = false;

			if (fill)
				Gl.glBegin(Gl.GL_QUADS);
			else
				Gl.glBegin(Gl.GL_LINE_LOOP);
			Gl.glVertex2i(rect.X, rect.Y);
			Gl.glVertex2i(rect.X, rect.Y+rect.Height);
			Gl.glVertex2i(rect.X+rect.Width, rect.Y+rect.Height);
			Gl.glVertex2i(rect.X+rect.Width, rect.Y);
			Gl.glEnd();
			Texturing = true;
		}



		/// <summary>
		/// Draws a line from point "from" to point "to"
		/// </summary>
		/// <param name="from"></param>
		/// <param name="to"></param>
		static public void Line(Point from, Point to)
		{
			Texturing = false;
			Gl.glBegin(Gl.GL_LINES);
			Gl.glVertex2i(from.X, from.Y);
			Gl.glVertex2i(to.X, to.Y);
			Gl.glEnd();
			Texturing = true;
		}


		/// <summary>
		/// Draws a point
		/// </summary>
		/// <param name="point"></param>
		public static void Plot(Point point)
		{
			Texturing = false;
			Gl.glBegin(Gl.GL_POINTS);
			Gl.glVertex2i(point.X, point.Y);
			Gl.glEnd();
			Texturing = true;
		}

		/// <summary>
		/// Draws a Texture on the screen and resize it
		/// </summary>
		/// <param name="rect">Rectangle on the screen</param>
		/// <param name="tex">Rectangle in the texture</param>
		/// <param name="mode">Rendering mode</param>
		public static void Blit(Rectangle rect, Rectangle tex, TextureLayout mode)
		{
			// Display mode
			switch (mode)
			{
				default:
				{
					Blit(tex, tex);
				}
				break;


				case TextureLayout.Stretch:
				{
					Video.Blit(rect, Texture.Rectangle);
				}
				break;


				case TextureLayout.Center:
				{
					Point pos = new Point(
						(rect.Width - Texture.Size.Width) / 2,
						(rect.Height - Texture.Size.Height) / 2
						);
					pos.Offset(rect.Location);
					Blit(Texture, pos);
				}
				break;


				case TextureLayout.Tile:
				{
					Video.ScissorZone = rect;
					Video.Scissor = true;

					for (int y = rect.Location.Y; y < rect.Location.Y + rect.Height; y += Texture.Size.Height)
						for (int x = rect.Location.X; x < rect.Location.X + rect.Width; x += Texture.Size.Width)
						{
							Blit(Texture, new Point(x, y));
						}

					Video.Scissor = false;
				}
				break;

				case TextureLayout.Zoom:
				{
					int value = Math.Min(rect.Width - Texture.Width, rect.Height - Texture.Height);

					Rectangle final = new Rectangle(
						0, 0,
						Texture.Width + value, Texture.Height + value);
					
					final.Location = new Point ((rect.Width - final.Width) / 2,
												(rect.Height - final.Height) / 2);

					Video.Blit(final, Texture.Rectangle);

				}
				break;
			}
		}




		/// <summary>
		/// Blits a texture to the screen.
		/// Texture must be bound before use.
		/// </summary>
		/// <param name="rect">Rectangle on the screen</param>
		/// <param name="tex">Rectangle in the texture</param>
		static public void Blit(Rectangle rect, Rectangle tex)
		{
			Gl.glBegin(Gl.GL_QUADS);
			RawBlit(rect, tex);
			Gl.glEnd();
		}


		/// <summary>
		/// Blits a texture on the screen
		/// </summary>
		/// <param name="tex"></param>
		/// <param name="pos"></param>
		static public void Blit(Texture tex, Point pos)
		{
			if (tex == null)
				return;

			Texture = tex;

			Gl.glBegin(Gl.GL_QUADS);
			RawBlit(new Rectangle(pos.X, pos.Y, tex.Width, tex.Height), tex.Rectangle);
			Gl.glEnd();
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="tex"></param>
		/// <param name="rect"></param>
		/// <param name="mode"></param>
		static public void Blit(Texture tex, Rectangle rect, TextureLayout mode)
		{
			Texture = tex;

			Blit(rect, tex.Rectangle, mode);
		}


		/// <summary>
		/// Raw blit a texture to the screen (no call to GlBegin()/GlEnd())
		/// </summary>
		/// <param name="rect">Rectangle on the screen</param>
		/// <param name="tex">Rectangle in the texture</param>
		static public void RawBlit(Rectangle rect, Rectangle tex)
		{
			Gl.glTexCoord2i(tex.X, tex.Y);
			Gl.glVertex2i(rect.X, rect.Y);
			Gl.glTexCoord2i(tex.X, tex.Y + tex.Height);
			Gl.glVertex2i(rect.X, rect.Y+rect.Height);
			Gl.glTexCoord2i(tex.X+tex.Width, tex.Y+tex.Height);
			Gl.glVertex2i(rect.X+rect.Width, rect.Y+rect.Height);
			Gl.glTexCoord2i(tex.X+tex.Width, tex.Y);
			Gl.glVertex2i(rect.X+rect.Width, rect.Y);
		}

		#endregion


		#region Fonts



		/// <summary>
		/// Prints some text on the screen
		/// </summary>
		/// <param name="pos">Offset of the text</param>
		/// <param name="text">Texte to print</param>
		static public void DrawText(Point pos, string text)
		{
			if (Font == null || Font.TileSet == null)
				return;

			Color oldColor = Video.Color;
			Video.Color = Font.Color;


			// For each caracter in the text
			foreach (char c in text)
			{
				Font.TileSet.Draw(c, pos);

				Tile tile = Font.TileSet.GetTile(c);
				if (tile != null)
					pos.X += tile.Rectangle.Width;
			}

			Video.Color = oldColor;
			
		}


		#endregion


		#region Internal resources

		/// <summary>
		/// Texture for ResourceSet
		/// </summary>
		static Texture SpawnPointTexture;



		#endregion

		#region Properties


		/// <summary>
		/// Gets/sets the font used to print
		/// </summary>
		static public TTFFont Font
		{
			get
			{
				return font;
			}
			set
			{
				font = value;

				if (!font.IsGenerated)
					font.Generate(font.FileName, font.Size, font.Style);
			}
		}
		static TTFFont font;


		/// <summary>
		/// Gets/sets the viewport
		/// </summary>
		static public Rectangle ViewPort
		{
			get
			{
				return viewPort;
			}
			set
			{
				viewPort = value;
				Gl.glViewport(0, 0, viewPort.Width, viewPort.Height);
				Gl.glMatrixMode(Gl.GL_PROJECTION);
				Gl.glLoadIdentity();
				Gl.glOrtho(viewPort.Left, viewPort.Width, viewPort.Height, viewPort.Top, -1, 1);
				Gl.glMatrixMode(Gl.GL_MODELVIEW);
				Gl.glLoadIdentity();
			}
		}
		static Rectangle viewPort;

		/// <summary>
		/// Gets/sets the cleacolor
		/// </summary>
		static public Color ClearColor
		{
			get
			{
				return clearColor;
			}
			set
			{
				clearColor = value;
				Gl.glClearColor(clearColor.R/255.0f, clearColor.G/255.0f, clearColor.B/255.0f, clearColor.A/255.0f);
			}
		}
		static Color clearColor;



		/// <summary>
		/// Enables/disables face culling
		/// </summary>
		public static bool Culling
		{
			set
			{
				if (value)
					Gl.glEnable(Gl.GL_CULL_FACE);
				else
					Gl.glDisable(Gl.GL_CULL_FACE);
			}

			get
			{
				return (Gl.glIsEnabled(Gl.GL_CULL_FACE) == 1);
			}
		}


		/// <summary>
		/// Enables/disables depth test
		/// </summary>
		public static bool DepthTest
		{
			set
			{
				if (value)
					Gl.glEnable(Gl.GL_DEPTH_TEST);
				else
					Gl.glDisable(Gl.GL_DEPTH_TEST);
			}

			get
			{
				return (Gl.glIsEnabled(Gl.GL_DEPTH_TEST) == 1);
			}
		}


		/// <summary>
		/// Gets / sets the current texture
		/// </summary>
		public static Texture Texture
		{
			set
			{
				// If texture already bound 
				if (value == null || texture == value)
					return;
				
				texture = value;

				// Is texture valid ?
				if (texture.GlId == -1)
				{
					Log.Send(new LogEventArgs(LogLevel.Warning, "Texture \"" + texture.Name + "\" (@\"" + texture.FileName + "\") cache miss", null));
/*
					if (texture.Regenerate() == false)
					{
						Log.Send(new LogEventArgs(LogLevel.Error, "Second stage loading texture \"" + texture.Name + "\" (@\"" + texture.FileName + "\") failed !", null));
						return;
					}
*/
				}

				// Modification de la matrice de texture afin de pouvoir indexer les pixels un a un
				Gl.glBindTexture(Gl.GL_TEXTURE_2D, texture.GlId);
				Gl.glMatrixMode(Gl.GL_TEXTURE);
				Gl.glLoadIdentity();
				Gl.glScalef(1.0f/texture.Size.Width, 1.0f/texture.Size.Height, 1.0f);
			}
			get
			{
				return texture;
			}
		}
		static Texture texture;


		/// <summary>
		/// Size of a point
		/// </summary>
		public static float PointSize
		{
			get
			{
				return pointSize;
			}
			set
			{
				Gl.glPointSize(value);
				pointSize = value;
			}
		}
		static float pointSize;

		/// <summary>
		/// Width of a line
		/// </summary>
		public static float LineWidth
		{
			get
			{
				return lineWidth;
			}
			set
			{
				Gl.glLineWidth(value);
				lineWidth = value;
			}
		}
		static float lineWidth;



		/// <summary>
		/// Gets/sets blending state
		/// </summary>
		public static bool Blending
		{
			get
			{
				return blending;
			}
			set
			{
				blending = value;
				if (value)
					Gl.glEnable(Gl.GL_BLEND);
				else
					Gl.glDisable(Gl.GL_BLEND);

				// Alpha transparency
				Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
			}
		}
		static bool blending;


		/// <summary>
		/// Enables/disables 2d texture
		/// </summary>
		public static bool Texturing
		{
			get
			{
				return Gl.glIsEnabled(Gl.GL_TEXTURE_2D) == 1;
			}
			set
			{
				if (value)
					Gl.glEnable(Gl.GL_TEXTURE_2D);
				else
					Gl.glDisable(Gl.GL_TEXTURE_2D);

			}
		}


		/// <summary>
		/// Gets / sets the current color
		/// </summary>
		public static Color Color
		{
			get
			{
				return color;
			}
			set
			{
				color = value;
				Gl.glColor4ub(color.R, color.G, color.B, color.A);
			}
		}
		static Color color = Color.White;


		/// <summary>
		/// Gets/Sets the scissor test
		/// </summary>
		public static bool Scissor
		{
			get
			{
				return scissor;
			}
			set
			{
				if (value == scissor)
					return;
				scissor = value;
				if (scissor)
					Gl.glEnable(Gl.GL_SCISSOR_TEST);
				else
					Gl.glDisable(Gl.GL_SCISSOR_TEST);
			}
		}
		static bool scissor;


		/// <summary>
		/// Gets/sets the scissor zone
		/// </summary>
		public static Rectangle ScissorZone
		{
			get
			{
				return scissorZone;
			}
			set
			{
				scissorZone = value;
				//Gl.glScissor(scissorZone.X,scissorZone.Bottom, scissorZone.Width,  scissorZone.Top - scissorZone.Bottom);
				Gl.glScissor(value.X, viewPort.Height - value.Top - value.Height, value.Width, value.Height);
			}
		}
		static Rectangle scissorZone;


		/// <summary>
		/// Translates visuals output
		/// </summary>
		public static Point Offset
		{
			get
			{
				return offset;
			}
			set
			{
				offset = value;
				Gl.glMatrixMode(Gl.GL_MODELVIEW);
				Gl.glLoadIdentity();
				Gl.glTranslatef(offset.X, offset.Y, 0);
			}
		}
		static Point offset;



		/// <summary>
		/// Returns weither the Video engine is initialized
		/// </summary>
		public static bool IsInit
		{
			get
			{
				return isInit;
			}
		}
		static bool isInit = false;




		/// <summary>
		/// Is texture size limited to power of two size ?
		/// </summary>
		static public bool NonPowerOfTwoTextureSupported
		{
			get
			{
				return nonPowerOfTwoTextureSupported;
			}
		}
		static bool nonPowerOfTwoTextureSupported;


		/// <summary>
		/// If true Quit the application
		/// </summary>
		static internal bool quitFlag = false;

		/// <summary>
		/// This function returns the name of the graphic card vendor. 
		/// </summary>
		static public string VideoVendor
		{
			get
			{
				return Gl.glGetString(Gl.GL_VENDOR);
			}
		}


		/// <summary>
		/// This function returns the name of the graphic card. 
		/// </summary>
		static public string VideoRenderer
		{
			get
			{
				return Gl.glGetString(Gl.GL_RENDERER);
			}
		}



		/// <summary>
		/// This function returns OpenGL version
		/// </summary>
		static public string VideoVersion
		{
			get
			{
				return Gl.glGetString(Gl.GL_VERSION);
			}
		}



		/// <summary>
		/// This function returns the OpenGL Shading Language version that is supported by the engine
		/// </summary>
		static public string ShadingLangageVersion
		{
			get
			{
				return Gl.glGetString(Gl.GL_SHADING_LANGUAGE_VERSION);
			}
		}



		/// <summary>
		/// This function returns a space-separated list of OpenGL supported extensions
		/// </summary>
		static public string VideoExtensions
		{
			get
			{
				return Gl.glGetString(Gl.GL_EXTENSIONS);
			}
		}



		/// <summary>
		/// Initial OpenGL context
		/// </summary>
		static SimpleOpenGlControl HiddenGlControl;

		/// <summary>
		/// Intitial rendering context
		/// </summary>
		static IntPtr HiddenGlControlContext;

		#endregion

		
		#region Draw Form


		/// <summary>
		/// Rendering Form
		/// </summary>
		static internal RenderForm Form
		{
			get
			{
				return formHandle;
			}
		}
		static RenderForm formHandle = null;

		
									

		/// <summary>
		/// Gets if the application if active
		/// </summary>
		/// TODO à faire
		public static bool IsActive
		{
			get
			{
				return true; // Sdl.Video.IsActive;
			}
		}


		/// <summary>
		/// Gets/sets window caption
		/// </summary>
		public static string WindowCaption
		{
			get
			{
				return formHandle.Text;
			}
			set
			{
				formHandle.Text = value;
			}
		}



		/// <summary>
		/// Gets/sets the size of the rendering form
		/// </summary>
		static public Size Size
		{
			get
			{
				return formHandle.Size;
			}
			set
			{
				formHandle.Size = value;
			}
		}

									

		/// <summary>
		/// Gets/sets the fullscreen mode
		/// </summary>
		static public bool FullScreen
		{
			get
			{
				return fullScreen;
			}

			set
			{
				if (value == fullScreen)
					return;


				fullScreen = value;
				if (fullScreen)
				{
				}
				else
				{
				}
			}
		}
		static bool fullScreen = false;

		#endregion


		#region Frame rate


/*
		/// <summary>
		/// Gets/sets the wanted framerate
		/// </summary>
		static public int Fps
		{
			get
			{
				return (int)(1000 / formHandle.FpsTimer.Interval);
			}
			set
			{
				if (value <= 0)
					return;

				formHandle.FpsTimer.Interval = 1000/ value;
			}
		}
*/
		/// <summary>
		/// Gets when the last frame tick occurred. 
		/// </summary>
		//static int lastTick;


		/// <summary>
		/// Waits for the VBL
		/// </summary>
		static public bool WaitVbl
		{
			get
			{
				return waitVbl;
			}
			set
			{
				waitVbl = value;

				if (waitVbl)
					Wgl.wglSwapIntervalEXT(1);
				else
					Wgl.wglSwapIntervalEXT(0);
			}
		}
		static bool waitVbl;

									


		#endregion


		#region Events

		/// <summary>
		/// Event fred every time a frame need to be rendered
		/// </summary>
		public static event EventHandler<FrameTickArgs> OnFrameTick;

		/// <summary>
		/// Event fired when the engine is closing
		/// </summary>
		public static event EventHandler<QuitEventArgs> OnClosing;


		#endregion
	}

	/// <summary>
	/// Arguments for the QuitEvent. 
	/// </summary>
	public class QuitEventArgs : EventArgs
	{
	}

	/// <summary>
	/// Arguments for the OnFrameTick event
	/// </summary>
	public class FrameTickArgs : EventArgs
	{
	}

}
